package es.deusto.herotactics.ingameandroid;

import java.util.ArrayList;

import jugador.Jugador;
import logica.ModeloHT;
import logica.Partida;
import mapa.Coordenada;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import es.deusto.herotactics.utils.Game;

/**
 * Mapa scrollable por la pantalla y gestor de la pantalla durante el juego en general.
 * Gestiona las pulsaciones según la coordenada en la que se ha pulsado, teniendo en cuenta
 * los objetos que tiene en cada una de las posiciones.
 * 
 * Por motivos de rendimiento, está preparado para arrancar el hilo de dibujado en la pantalla
 * cada vez que se quiera dibujar en ella y en cuanto se acabe de dibujar, habrá que parar el hilo.
 * Para refrescar la pantalla una sola vez, utilizar arancarHilo(false).
 * 
 * @author Ibai
 *
 */
public class PantallaAndroid extends SurfaceView implements SurfaceHolder.Callback {
	//tamaño de la barra superior, no es final por si mas adelante voy a querer que cambie en ejecución
	private static int tamanyoBarra = 50;
	//Tamaño de la celda, no es final porque esto está preparado para ser 'zoomable' pero está sin implementar
	private static int tamanyoCelda = 60;
	//El mapa es una array bidimensional de Celdas
	private Celda[][] mapa;
	
	//Celdas mostradas en pantalla
	private int filaIni;
	private int filaFin;
	private int colIni;
	private int colFin;
	
	//compensación del scroll en las celdas
	private int xScrollOffset = 0;
	private int yScrollOffset = 0;
	
	//compensación del punto superior izquierdo del mapa
	private int xOffset = 0;
	private int yOffset = 0;
	
	//margen de error entre pulsación y scroll
	private int margenScroll = 10;
	
	//indica si se está moviendo la pulsación
	private boolean moviendo = false;
	
	//Coordenada de pulsación
	private Coordenada pulsado = new Coordenada();
	
	private HiloMapa hilo;
	private Paint paint = new Paint();
	private Barra barra;
	private Contexto contexto;
	
	//para saber en medio de que acción está
	private CurrentAction currentAction = null;
	
	private ArrayList<Celda> celdasDesactivadas;
		
	private ModeloHT modeloHT;
	
	/**
	 * Contructor del mapa y la pantalla durante el juego en general.
	 * Al rellenar el Mapa con Celdas va recogiendo las imágenes de los
	 * terrenos y las unidades que hay en cada posición y se las va asignando a los objetos Celda.
	 * 
	 * @param context el contexto en el que está el mapa
	 * @param x El ancho del mapa
	 * @param y El alto del mapa
	 */
	public PantallaAndroid(Context context, ModeloHT modeloHT) {
		super(context);
		this.modeloHT = modeloHT;
		Partida partida = modeloHT.getPartida();
		barra = new Barra(this, tamanyoBarra);
		celdasDesactivadas = new ArrayList<Celda>();
		Coordenada coordMax = partida.getMapa().getCoordenadaMaxima();
		int mapWidth = coordMax.getX()+1;
		int mapHeight = coordMax.getY()+1;
		//Comprobar que el mapa tiene tamaño
		if (mapWidth <= 0 || mapHeight <= 0)
			throw new RuntimeException("El mapa tiene que tener mínimo una fila y una columna");
		//meter las celdas en el array bidimensional
		mapa = new Celda[mapWidth][mapHeight];
		//asignar turno a jugador en la barra superior
		Game.asignarTurno(this, partida.getTurno().getJugadorActivo());
		
		String imagesFolder = Game.pathAHT;
		for (int i=0;i<mapWidth;i++)
			for (int j=0;j<mapHeight;j++) {
				mapa[i][j] = new Celda(this, new Coordenada(i,j), tamanyoCelda, imagesFolder);
			}
		// registrar la view al SurfaceHolder
        getHolder().addCallback(this);
        
        // crear el hilo
        hilo = new HiloMapa(this);
        
        // asegurarse de que el view es focusable
        setFocusable(true);
	}
	
	/**
	 * Método para dibujar en pantalla la parte del mapa a visualizar
	 * 
	 * @param canvas El canvas en el que se dibujará el mapa
	 */
	@Override
	public void onDraw (Canvas canvas) {
		tamanyoCelda = mapa[0][0].getTamanyo();
		tamanyoBarra = barra.getTamanyo();
		for (int i = colIni; i<colFin; i++) {
			for (int j=filaIni; j<filaFin; j++) {
				//calcular la posición correcta para mostrar la celda.
				//TIP: si se quita el offset de la operación hará scroll celda a celda
				int x = (i-colIni)*tamanyoCelda - xScrollOffset;
				int y = (j-filaIni)*tamanyoCelda - yScrollOffset;
				Coordenada posicion = new Coordenada(x,y);
				paint.setAntiAlias(true);
				mapa[i][j].draw(canvas, paint, posicion);
			}
		}
		barra.draw(canvas, getWidth(), paint);
		contexto.draw(canvas, paint);
	}
	
	/**
	 * Controlar los diferentes eventos cuando el usuario toca la pantalla.
	 * Como algunas pantallas son muy sensibles y cualquier pulsación la toman
	 * como movimiento, se ha puesto un margen de error para que los movimientos mínimos
	 * se tomen como pulsaciones y no como movimientos. Este valor está en la variable
	 * margenScroll
	 * 
	 * @param event El evento realizado por el usuario al tocar la pantalla
	 */
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		if (event.getAction() == MotionEvent.ACTION_DOWN) {
			tamanyoCelda = mapa[0][0].getTamanyo();
			//guardar las coordenadas de la pulsación
            pulsado.setXY((int)event.getX(),(int)event.getY());
        } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
        	int xPrev = pulsado.getX();
        	int yPrev = pulsado.getY();
        	int xAct = (int) event.getX();
        	int yAct = (int) event.getY();
        	/*
        	 * Algunas pantallas son muy sensibles y cualquier pulsación la notan como movimiento,
        	 * con esto se le concede un margen de error
        	 */
        	if ((Math.abs(xAct-xPrev) > margenScroll || Math.abs(yAct-yPrev) > margenScroll) && !contexto.getActivo() && yPrev > tamanyoBarra) {
        	
	            //el usuario mueve el dedo, activar el flag
	            moviendo = true;
	            
	            //arrancar el hilo
	            arrancarHilo(true);
	            
	            //coger la nueva compensación
	            xOffset += xPrev - xAct;
	            yOffset += yPrev - yAct;
	            
	            // comprobar que la compensación no sale de los límites
	            tamanyoCelda = mapa[0][0].getTamanyo();
	            int maxTemp = mapa.length * tamanyoCelda - getWidth();
	            if (xOffset < 0) {
	                xOffset = 0;
	            } else if (xOffset > maxTemp) {
	                xOffset = maxTemp;
	            }
	            maxTemp = mapa[0].length * tamanyoCelda - getHeight()+tamanyoBarra;
	            if (yOffset < 0) {
	                yOffset = 0;
	            } else if (yOffset > maxTemp) {
	            	yOffset = maxTemp;
	            }
	            
	            //guardar las coordenadas de la última pulsación
	            pulsado.setXY(xAct,yAct);
	            
	            calcularCeldasMostradas();
        	}
        } else if (event.getAction() == MotionEvent.ACTION_UP) {
           //el usuario levanta el dedo
            if (!moviendo && !contexto.getActivo()) {
            	//la pulsación se hace en la barra superior
            	if (event.getY() < tamanyoBarra) {
            		barra.touch(event.getX(),event.getY());
            	} else { //la pulsación se hace en el mapa
	            	//calcular la celda pulsada
            		tamanyoCelda = mapa[0][0].getTamanyo();
	                int column = (int) Math.ceil((xOffset + event.getX()) / tamanyoCelda) - 1;
	                int fila = (int) Math.ceil((yOffset + event.getY()-tamanyoBarra) / tamanyoCelda) - 1;
	                Celda celda = mapa[column][fila];
	                celda.touch();
            	}
            	arrancarHilo(false);
            } else if (contexto.getActivo()) { //si está en el menú contextual
            	contexto.touch(event.getX(), event.getY());
            	arrancarHilo(false);
            } else if (moviendo) { //si se está moviendo
            	//Reiniciar el flag
            	moviendo = false;
            	//Parar el hilo
            	pararHilo();
            }
        }
		return true;
	}
	
	/**
	 * Calcula las celdas que hay que mostrar: las que están en el rango de mostrado de la
	 * pantalla
	 */
    private void calcularCeldasMostradas() {
        filaIni = yOffset / tamanyoCelda;
        filaFin = (int) Math.min(mapa[0].length, filaIni + Math.floor(getHeight() / tamanyoCelda) + 2);
        colIni = xOffset / tamanyoCelda;
        colFin = (int) Math.min(mapa.length, colIni + Math.floor(getWidth() / tamanyoCelda) + 2);
        xScrollOffset = (int) (((xOffset / (float) tamanyoCelda) - (xOffset / tamanyoCelda)) * tamanyoCelda);
        yScrollOffset = (int) (((yOffset / (float) tamanyoCelda) - (yOffset / tamanyoCelda)) * tamanyoCelda)-tamanyoBarra;
    }
    
    public Contexto getContexto() {
    	return contexto;
    }
    
    /**
     * Devuelve la acción que se está ejecutando
     * 
     * @param returnNull Definir si se quiere que el método devuelva null cuando no hay una acción en
     * curso o que devuelva una acción vacía (a true si se va a ejecutar un método de la clase devuelta
     * y no se sabe si actualmente hay una acción en ejecución
     * @return objeto con la acción que se está ejecutando
     */
    public CurrentAction getCurrentAction(boolean returnNull) {
    	if (!returnNull && currentAction == null)
    		return new CurrentAction();
    	return currentAction;
    }
    
    public void setCurrentAction(CurrentAction currentAction) {
    	this.currentAction = currentAction;
    }
    
    public ModeloHT getModeloHT() {
    	return modeloHT;
    }
    
    public Partida getPartida() {
    	return modeloHT.getPartida();
    }
    
    public Barra getBarra() {
    	return barra;
    }
    
    public Celda getCelda(Coordenada coord) {
    	return mapa[coord.getX()][coord.getY()];
    }
    
    public Activity getActivity() {
    	return (Activity) getContext();
    }
    
    public void addCeldaDesactivada(Celda celda) {
    	celda.setEstado(EstadoCelda.DESACTIVADA);
    	celdasDesactivadas.add(celda);
    }
    
    public void resetCeldasDesactivadas() {
    	for (Celda celda : celdasDesactivadas) {
    		celda.setEstado(EstadoCelda.NORMAL);
    	}
    	celdasDesactivadas.clear();
    }
    
    /**
     * Se ejecuta cuando un jugador gana.
     * Muestra una alerta informativa del jugador que ha ganado y cierra la partida
     * 
     * @param j Jugador que ha ganado
     */
    public void jugadorGana(Jugador j) {
    	AlertDialog.Builder builder = new AlertDialog.Builder(this.getContext());
		builder.setMessage("¡El jugador "+j.getNombre()+" ha ganado!")
		.setCancelable(false)
		.setPositiveButton("Aceptar", new DialogInterface.OnClickListener() {
			public void onClick(DialogInterface dialog, int id) {
				PantallaAndroid.this.getActivity().finish();
			}
		});
		AlertDialog alert = builder.create();
		alert.show();
    }
    
	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width,
			int height) {
		//No hacer nada, en un principio no va a saltar		
	}

	/**
	 * Se lanza al iniciar un SurfaceView
	 * Inicia el hilo si no lo está, pero solo hace un dibjado
	 */
	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		calcularCeldasMostradas();
		arrancarHilo(false);
        
        // declarar el menú de contexto
		contexto = new Contexto(30,30,40,40,getWidth(),getHeight());
	}

	/**
	 * Se llama cuando se destruye un SurfaceView
	 * Para el hilo
	 */
	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		pararHilo();
	}
	
	/**
	 * Inicia el hilo para refrescar la pantalla
	 * 
	 * @param continuar Para saber si va a ser un único refresco o se va a mantener refrescando
	 */
	private void arrancarHilo(boolean continuar) {
		//si no se ha arrancado ya el hilo, se arranca
		if (!hilo.isAlive()) {
			hilo = new HiloMapa(this);
		}
		if (!hilo.getContinuar()) {
        	hilo.setContinuar(continuar);
        	hilo.start();
        }
	}
	
	/**
	 * Hace un único refresco de pantalla puntual
	 */
	public void refrescarPantalla() {
		arrancarHilo(false);
	}
	
	/**
	 * Para el hilo del dibujado de la pantalla
	 */
	private void pararHilo() {
		if (hilo.getContinuar())
	        hilo.setContinuar(false);
		boolean retry = true;
       while (retry) {
            try {
                retry = false;
                hilo.join();
            } catch (InterruptedException e) {
                //se seguirá intentando
            }
        }
	}
}
